package compiler;

import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

import javax.tools.JavaCompiler;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.FileOutputStream;

import static org.objectweb.asm.Opcodes.*;

public class RunnerClassCompiler {
    static String funcRunName = "run"; //name of method, which runs function
    String programName;
    String mainFuncClassName;
    String createdClassName;
    ClassWriter writer;


    public RunnerClassCompiler(String programName, String inputPoint, String createdClassName) {
        mainFuncClassName = inputPoint;
        this.programName = programName;
        this.createdClassName = createdClassName;
    }

    public void compile() {
        createFunctionInterface();
        createNumberWrapper();
        createObjectWrapper();
        writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
        writer.visit(V1_7, ACC_PUBLIC, createdClassName, null, "java/lang/Object", null);
        //..x;
        MethodVisitor methodWriter = writer.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "main",
                "([Ljava/lang/String;)V", null, null);
        methodWriter.visitCode();
        methodWriter.visitTypeInsn(NEW, mainFuncClassName);
        methodWriter.visitInsn(DUP);
        methodWriter.visitMethodInsn(Opcodes.INVOKESPECIAL, mainFuncClassName, "<init>", "()V");
        methodWriter.visitVarInsn(ASTORE, 0);
        methodWriter.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
        methodWriter.visitVarInsn(ALOAD, 0);
        methodWriter.visitMethodInsn(Opcodes.INVOKEVIRTUAL, mainFuncClassName, funcRunName,
                "()LFunctionInterface;");
        methodWriter.visitMethodInsn(Opcodes.INVOKEINTERFACE, "FunctionInterface", "getValue",
                "()Ljava/lang/Object;");
        methodWriter.visitTypeInsn(CHECKCAST, "java/lang/Integer");
        methodWriter.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I");
        methodWriter.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(I)V");
        methodWriter.visitInsn(RETURN);
        methodWriter.visitInsn(POP);
        methodWriter.visitMaxs(0, 0);
        methodWriter.visitEnd();
        writer.visitEnd();
        save();
    }

    private void createNumberWrapper() {
        String fileToCompile = "Number.java";
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

        int compilationResult = compiler.run(null, null, null, fileToCompile);
        if (compilationResult == 0) {
            System.out.println("Compilation is successful");
        } else {
            System.out.println("Compilation Failed");
        }
    }

    private void createObjectWrapper() {
        String fileToCompile = "ObjectWrapper.java";
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

        int compilationResult = compiler.run(null, null, null, fileToCompile);
        if (compilationResult == 0) {
            System.out.println("Compilation is successful");
        } else {
            System.out.println("Compilation Failed");
        }
    }

    private void createFunctionInterface() {
        writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
        writer.visit(V1_7, ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE,
                "FunctionInterface", null, "java/lang/Object", null);
        writer.visitMethod(ACC_PUBLIC + ACC_ABSTRACT, "run",
                "()LFunctionInterface;", null, null).visitEnd();
        writer.visitMethod(ACC_PUBLIC + ACC_ABSTRACT, "add", "(Ljava/lang/Object;)V", null, null).visitEnd();
        //writer.visitMethod(ACC_PUBLIC + ACC_ABSTRACT, "need", "()I", null, null).visitEnd();
        writer.visitMethod(ACC_PUBLIC + ACC_ABSTRACT, "getValue", "()Ljava/lang/Object;", null, null).visitEnd();
        writer.visitEnd();
        byte[] code = writer.toByteArray();
        try {
            FileOutputStream writer = new FileOutputStream(new File("FunctionInterface" + ".class"));
            writer.write(code);
            writer.flush();

        } catch (Exception e) {
            System.err.println("Can't write function interface class to file");
        }

    }


    private void save() {
        byte[] code = writer.toByteArray();
        try {
            FileOutputStream writer = new FileOutputStream(new File(createdClassName + ".class"));
            writer.write(code);
            writer.flush();

        } catch (Exception e) {
            System.err.println("Can't write main class to file");
        }
    }
}
